/* filename : FFDeckLinkBuffer.h
 * 다음을 개선하였다.
 * 1. audioPacket(IDeckLinkAudioInputPacket)이 NULL이 발생하더라고 pts를 조절하고 data에 "0"을 채워넣도록 수정하였음
 */
#ifndef __FFDECKLINKBUFFER_H__
#define __FFDECKLINKBUFFER_H__

#include <string.h>

/* AudioPkt
 * DeckLink의 Audio stream을 Sample당 기억하는 structure
 */
typedef struct AudioPkt{
	/* buf
	 * deckLink Audio stream 바로 기억하는 buffer
	 * 실제 사용하는 크기 6408 or 6404
	 */
	uint8_t buf[6500];

	/* pts
	 * deckLink Audio의 time stamp
	 */
	int64_t pts;

	/* wsize
	 * buf에 write한 크기
	 */
	int wsize;//write size

	/* rsize
	 * buf에서 read한 크기
	 */
	int rsize;//read size

	/* duration
	 * buf에 들어있는 time stamp의 길이(시간)
	 * (wsize - rsize) / sizePerPts = duration
	 */
	int duration;

	/* sizePerPts
	 * 1pts당 크기(Bytes)
	 * 16bits * 2channel = 32bits = 4 Bytes
	 */
	int sizePerPts;// 1pts 당 size  ==> 4
}AudioPkt;

/* FFDeckLinkBuffer
 * DeckLink의 auido의 framesize가 4096보다 커서 4096단위로 전송하기 위한 buffer
 */
class FFDeckLinkBuffer
{
public:
	FFDeckLinkBuffer(void);//생성자
	~FFDeckLinkBuffer(void);//소멸자

	/* char push(uint8_t *buf, int size, int64_t pts, int duration)
	 * Parameters:
	 *  [int]   buf; mAudioPkt에 넣을 data를 갖고있는 pointer
	 *         size; buf의 size
	 *          pts; buf의 time stamp
	 *     duration; buf의 duration
	 * Returns:
	 *    0 = success; 정상적으로 buf를 mAudioPkt에 복사하였다.
	 *   -1 = fail; buf가 NULL이다.
	 *   -2 = full mAudioPkt; 비어있는 mAudioPkt이 없다.
	 *
	 * mAudioPkt[2]안에 하나씩 교차해서 buf를 넣는다.
	 * 넣을때 size, pts, duration을 함께 넣는다.
	 * mAudioPkt에 넣을때 데이터가 비어있을때 넣는다. 즉,wsize가 0인 것을 확인하고 넣는다.
	 */
	char push(uint8_t *buf, int size, int64_t pts, int duration);

	/* int64_t pop(uint8_t **oBuf);
	 * Parameters:
	 *  [out] oBuf : 복사해 놓은 Audio DeckLink의 audio frame을 가지고 있는 pointer
	 *        size : DeckLink의 audio frame을 요청할 크기
	 * Returns:
	 *  int64_t; oBuf의 pts(timestamp)
	 *
	 * mAudioPkt에 있는 buf
	 *  oBuf의 size는 4096 duration은 1024로 항상 같다.
	 */
	int64_t pop(uint8_t **oBuf);

private:
	/* mBuf
	 * pop할 때 mAudioPkt의 데이터를 여기에 복사하여 mBufd의 주소를 알려준다.
	 * 초기값 : 모두 0
	 */
	uint8_t mBuf[4100];// 4096+4 // 6408*2

	/* mAudioPkt
	 * DeckLink의 audio frame을 2개까지 기억할 수 있는 버퍼.
	 * 초기값 : 모두 0
	 */
	AudioPkt mAudioPkt[2];

	/* mReadPkt
	 * mAudioPkt의 상대적인 위치를 기억하는 pointer
	 * mAudioPkt의 데이터를 4096크기로 조각낼때 사용한다.
	 * 초기값 : mAudioPkt[0]의 주소
	 */
	AudioPkt *mReadPkt;

	/* mSize
	 * DeckLink의 audio frame buffer의 남아 있는 크기.
	 * mAudioPkt[0]과 mAudioPkt[1]의 남아 있는 크기의 합
	 * (mAudioPkt[0].wsize-rsize) + (mAudioPkt[1].wsize-rsize) = mSize
	 * 초기값 : 0
	 */
	uint16_t mSize;

	/* void copyFromAudioPkt(uint8_t *buf, const uint64_t size);
	 * Parameters:
	 *    buf; 복사될 destination buffer
	 *    size; 복사할 크기(Byte)
	 * Returns:
	 *    void;
	 *
	 * 조각난 mReadPkt의 데이터(현재 읽고 있는 mAudioPkt)를 buf에 size만큼 복사할때 사용하는 함수
	 */
	void copyFromAudioPkt(uint8_t *buf, const uint64_t size);
};

FFDeckLinkBuffer::FFDeckLinkBuffer()
{
	mSize = 0;
	memset(&mAudioPkt[0], 0, sizeof(AudioPkt));
	memset(&mAudioPkt[1], 0, sizeof(AudioPkt));
	mReadPkt = mAudioPkt;
}

FFDeckLinkBuffer::~FFDeckLinkBuffer()
{

}

char FFDeckLinkBuffer::push(uint8_t *buf, int size, int64_t pts, int duration)
{
	AudioPkt *thisPkt = NULL;

	if(buf == NULL)
	{
		logW("Could not copy buf. buf is NULL\n");
		return -1;
	}

	if(mAudioPkt[0].wsize == 0)
		thisPkt = &mAudioPkt[0];
	else if(mAudioPkt[1].wsize == 0)
		thisPkt = &mAudioPkt[1];
	else
	{
		logW("Could not copy buf. mAudioPkt full.\n");
		return -2;
	}
	memcpy(thisPkt->buf, buf, size);
	thisPkt->wsize = size;
	thisPkt->rsize = 0;
	thisPkt->pts = pts;
	thisPkt->duration = duration;
	thisPkt->sizePerPts = size/duration;

	mSize += size;

	//logD("buffer pushed size %4d remainsize:%4d\n", size, mSize);
	return 0;
}

void FFDeckLinkBuffer::copyFromAudioPkt(uint8_t *buf, const uint64_t size)
{
	memcpy(buf, mReadPkt->buf + mReadPkt->rsize, size);
	mReadPkt->rsize += size;
	mReadPkt->duration -= (size/4);
	mSize -= size;
	mReadPkt->pts += (size/4);
}

int64_t FFDeckLinkBuffer::pop(uint8_t **oBuf)
{
	const uint16_t size = 4096;
	uint8_t *pBuf = mBuf;// pBuf는 mBuf내의 주소를 가리키는 pointer
	int64_t pts = 0;
	int remain = mReadPkt->wsize - mReadPkt->rsize;

	if(mSize < size)
	//두개 buffer(mAudioPkt[0], mAudioPkt[1])의 크기가 4096 보다 작을 경우;
	{
		*oBuf = NULL;
		return mReadPkt->pts;
	}
	else
	{
		if(remain >= size)
		//현재 읽을 AudioPkt의 beffer 크기가 4096 만큼 존재할 경우;
		{
			pts = mReadPkt->pts;
			copyFromAudioPkt(pBuf, size);
			//will return
		}
		else
		//조각 났을 경우
		{
			pts = mReadPkt->pts;
			copyFromAudioPkt(pBuf, remain);
			pBuf += remain;

			//logD("현재 AudioPkt에서 buffer 다 읽었음\n");
			if(mAudioPkt[0].pts == mAudioPkt[1].pts)
			//두개의 mAudioPkt pts가 잘 맞 물렸을 때
			{
				mReadPkt->wsize = 0;
				mReadPkt = (mReadPkt==&mAudioPkt[0])? &mAudioPkt[1]:&mAudioPkt[0];
				copyFromAudioPkt(pBuf, size - remain);
				//will return
			}
			else
			//audio Packet이 null이 발생하여 audio Packet을 push 하지 못 해서 pts가 맞지 않을 경우
			{
				int64_t nextPts = 0;
				AudioPkt *otherPkt = NULL;// mAudioPkt[0] <--> mAudioPkt[1]

				nextPts = mReadPkt->pts + ((size - remain)/4);

				otherPkt = (mReadPkt==&mAudioPkt[0])? &mAudioPkt[1]:&mAudioPkt[0];
				if(nextPts < otherPkt->pts)
				{
					//채우지 못한 audioPacket 채워주기. drop aduio packet 채우기
					memset(pBuf, 0, (nextPts - mReadPkt->pts)*4);
					mReadPkt->pts = nextPts;//+= ((size - remain)/4);
				}
				else
				{
					memset(pBuf, 0, (otherPkt->pts - mReadPkt->pts)*4);
					//mReadPkt->pts = otherPkt->pts;//otherPkt->pts + remain;
					mReadPkt->wsize = 0;
					mReadPkt = otherPkt;
					copyFromAudioPkt(pBuf, (otherPkt->pts - mReadPkt->pts)*4);
				}
				//will return
			}
		}
	}

	//logD("buffer poped remainsize:%4d\n", mSize);
	*oBuf = mBuf;
	return pts;
}

#endif
